package com.roomio.core.dao.impl;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;

import com.roomio.core.dao.GenericDAO;

/**
 * This class serves as the Base class for all other DAOs. Can be used for
 * standard CRUD operations.</p>
 * 
 * @author <a href="mailto:og.tech79@gmail.com">Shailendra Sinha</a>
 */
public class GenericJPADAO<T extends Serializable, ID extends Serializable> implements GenericDAO<T, ID> {
	protected final Log log = LogFactory.getLog(getClass());

	public static int DEFAULT_START_INDEX = 0;
	public static int DEFAULT_MAX_SIZE = 1000;

	@PersistenceContext(unitName = "persistenceUnit")
	protected EntityManager entityManager;

	protected Class<T> clazz = getClazz();

	public void setClazz(Class clazz) {
		this.clazz = clazz;
	}

	@SuppressWarnings("unchecked")
	public int count() {
		List list = this.entityManager.createQuery("select obj from " + this.clazz.getName() + " obj").getResultList();
		
		return list.size();
	}

	@SuppressWarnings("unchecked")
	public boolean exists(ID id) {
		T entity = this.entityManager.find(this.clazz, id);
		return entity != null;
	}

	@SuppressWarnings("unchecked")
	public T load(ID id) {
		return (T) this.entityManager.find(clazz, id);
	}

	@SuppressWarnings("unchecked")
	public T get(ID id) {
		T entity = this.entityManager.find(this.clazz, id);

		if (entity == null) {
			String msg = "'" + this.clazz + "' object with id '" + id + "' not found...";
			log.warn(msg);
			throw new EntityNotFoundException(msg);
		}
		return entity;
	}

	private int getStartIndex(Integer startIndex) {
		return (startIndex == null || startIndex < 0) ? DEFAULT_START_INDEX : startIndex;
	}

	private int getMaxResults(Integer maxResults) {
		return (maxResults < 0) ? DEFAULT_MAX_SIZE : maxResults;
	}

	@SuppressWarnings("unchecked")
	public List<T> findAll() {
		return findByCriteria(null, null);
	}

	@SuppressWarnings("unchecked")
	public List<T> findAll(Integer startRow, Integer maxResults) {
		return findByCriteria(startRow, maxResults);
	}

	@SuppressWarnings("unchecked")
	public T findById(ID id) {
		return load(id);
	}

	@SuppressWarnings("unchecked")
	public List<T> findByCriteria(T t) {
		return findByCriteria(null, null, Example.create(t)
		// .excludeZeroes()
				.ignoreCase().enableLike().enableLike(MatchMode.ANYWHERE));
	}

	@SuppressWarnings("unchecked")
	public List<T> findByCriteria(T t, Integer startRow, Integer maxResults) {
		return findByCriteria(startRow, maxResults, Example.create(t)
		// .excludeZeroes()
				.ignoreCase().enableLike().enableLike(MatchMode.ANYWHERE));
	}

	@SuppressWarnings("unchecked")
	public List<T> findByQuery(String queryString, String[] params, Object[] values) {
		return findByQuery(queryString, params, values, null, null);

	}

	@SuppressWarnings("unchecked")
	public List<T> findByQuery(String queryString, String[] params, Object[] values, Integer startRow, Integer maxResults) {
		Query query = this.entityManager.createQuery(queryString);
		int i = 0;
		for (String param : params) {
			query.setParameter(param, values[i]);
			i++;
		}
		List<T> list = null;
		try {
			query.setFirstResult(getStartIndex(startRow));
			if (maxResults != null)
				query.setMaxResults(getMaxResults(maxResults));
			list = query.getResultList();
		} catch (Exception ex) {
			log.error("Error occurred executing JPA query : " + queryString);
			ex.printStackTrace();
		}
		return list;

	}

	@SuppressWarnings("unchecked")
	public List<T> findByNamedQuery(String queryName, String[] params, Object[] values) {
		return findByNamedQuery(queryName, params, values, null, null);
	}

	@SuppressWarnings("unchecked")
	public List<T> findByNamedQuery(String queryName, String[] params, Object[] values, Integer startRow, Integer maxResults) {
		Query query = this.entityManager.createNamedQuery(queryName);
		int i = 0;
		for (String param : params) {
			query.setParameter(param, values[i]);
			i++;
		}
		List<T> list = null;
		try {
			query.setFirstResult(getStartIndex(startRow));
			if (maxResults != null)
				query.setMaxResults(getMaxResults(maxResults));
			list = query.getResultList();
		} catch (Exception ex) {
			log.error("Error occurred executing named query : " + queryName);
			ex.printStackTrace();
		}
		return list;
	}

	/**
	 * Use this inside subclasses as a convenience method.
	 */
	/*
	 * @SuppressWarnings("unchecked")
	 * public List<T> findByCriteria(Criterion... criterion) {
	 * return findByCriteria (null, null, criterion);
	 * }
	 */

	@SuppressWarnings("unchecked")
	public List<T> findByCriteria(Integer startRow, Integer maxResults, Criterion... criterion) {
		// get the native hibernate session
		Session session = (Session) this.entityManager.getDelegate();
		Criteria criteria = session.createCriteria(clazz);
		for (Criterion c : criterion) {
			criteria.add(c);
		}
		criteria.setFirstResult(getStartIndex(startRow));
		if (maxResults != null)
			criteria.setMaxResults(getMaxResults(maxResults));
		return criteria.list();
	}

	@SuppressWarnings("unchecked")
	public List<T> findByCriteria(Criteria criteria, Integer startRow, Integer maxResults) {
		criteria.setFirstResult(getStartIndex(startRow));
		if (maxResults != null)
			criteria.setMaxResults(getMaxResults(maxResults));
		return criteria.list();
	}

	@SuppressWarnings("unchecked")
	public List<T> findByCriteria(Criteria criteria) {
		return criteria.list();
	}

	@SuppressWarnings("unchecked")
	public Criteria createCriteria(T t) {
		// get the native hibernate session
		Session session = (Session) this.entityManager.getDelegate();
		Criteria criteria = session.createCriteria(clazz).add(Example.create(t)
		// .excludeZeroes()
				.ignoreCase()
				// .enableLike()
				.enableLike(MatchMode.ANYWHERE));
		return criteria;
	}

	@SuppressWarnings("unchecked")
	public void delete(T t) {
		this.entityManager.remove(t);
	}

	@SuppressWarnings("unchecked")
	public void deleteById(ID id) {
		Object obj = load(id);
		this.entityManager.remove(this.get(id));
	}

	@SuppressWarnings("unchecked")
	public void deleteAll() {
		Query query = this.entityManager.createQuery("delete " + clazz.getName());
		query.executeUpdate();
	}

	@SuppressWarnings("unchecked")
	public void save(T t) {
		this.entityManager.persist(t);
	}

	@SuppressWarnings("unchecked")
	public void update(T t) {
		this.entityManager.merge(t);
	}

	@SuppressWarnings("unchecked")
	public void update(List<T> tList) {
		for (T t2 : tList) {
			this.entityManager.merge(t2);
		}
	}

	@SuppressWarnings("unchecked")
	protected Class getClazz() {
		if (clazz == null) {
			ParameterizedType thisType = (ParameterizedType) getClass().getGenericSuperclass();
			clazz = (Class) thisType.getActualTypeArguments()[0];
		}
		return clazz;
	}

}
